package ClientProxy;

import java.io.IOException;
import java.rmi.AccessException;
import java.rmi.AlreadyBoundException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;
import java.util.Vector;

import javax.rmi.ssl.SslRMIClientSocketFactory;
import javax.rmi.ssl.SslRMIServerSocketFactory;

import General.Address;
import General.EventInterface;
import General.Product;
import General.ProxyInterface;
import General.QuoteImplementation;
import General.QuoteInterface;
import General.ShipInterface;

public class ProxyImplementation implements ProxyInterface {
	private Registry fRegistry;
	private AddressDatabaseImplementation fAddressDatabase;
	private HashMap<Product, String> fProductServer = new HashMap<Product, String>();
	private Vector<String> fServers = new Vector<String>();
	private int fRoundRobin;

	protected ProxyImplementation(Registry registry) throws RemoteException, NotBoundException, ClassNotFoundException, AlreadyBoundException {
		super();
		fRegistry = registry;
		fAddressDatabase = new AddressDatabaseImplementation();
		fRoundRobin = 0;
        UnicastRemoteObject.exportObject(this, 1093, new SslRMIClientSocketFactory(), new SslRMIServerSocketFactory());
		this.getServers();
	}
	
	private void getServers() throws NotBoundException, AccessException, RemoteException{
		for (String string : fRegistry.list()) {
			if (string.startsWith("Server_")) {
				if (!fServers.contains(string)) {
					fServers.add(string);
				}
			}
		}
		System.out.println("[PROXY] Servers available for next round: " + fServers.size());
	}

	public QuoteInterface quote(Product product, Address source, Address destination) throws RemoteException, NotBoundException {
		if (fRoundRobin >= fServers.size()) {
			this.getServers();
			fRoundRobin = 0;
		}
		
		// Check precondition
		if (fAddressDatabase.isValidAddress(source) && fAddressDatabase.isValidAddress(destination)) {
			ShipInterface shipInterface = (ShipInterface) fRegistry.lookup(fServers.get(fRoundRobin));
			System.out.println("[CALL] Quote on " + fServers.get(fRoundRobin));
			fRoundRobin++;
			return shipInterface.quote(product);
		}
		return null;
	}
	
	public boolean request(Product product, EventInterface event) throws RemoteException, NotBoundException, IOException {
		if (fRoundRobin >= fServers.size()) {
			this.getServers();
			fRoundRobin = 0;
		}
		
		// Check precondition
		if ((product.getTypeTransport().contentEquals(Product.AIR_PRIORITY)) && (product.isHazardous() || (product.getWeight() > 100))) {
			return false;
		}
				
		ShipInterface shipInterface = (ShipInterface) fRegistry.lookup(fServers.get(fRoundRobin));
		System.out.println("[CALL] Request on " + fServers.get(fRoundRobin));
		fProductServer.put(product, fServers.get(fRoundRobin));
		fRoundRobin++;

		return shipInterface.request(product, event);
	}
	
	public String status(Product product) throws RemoteException, NotBoundException {
		if (fProductServer.containsKey(product)) {
			String serverName = fProductServer.get(product);
			ShipInterface shipInterface = (ShipInterface) fRegistry.lookup(serverName);
			System.out.println("[CALL] Status on " + serverName);
			return shipInterface.status(product);
		} else {
			return "Product not found";
		}
	}
	
	public void changeRates(int rateGround, int rateAir, int rateAirPriority) throws RemoteException, NotBoundException, IOException {
		this.getServers();
		for (String string : fServers) {
			ShipInterface shipInterface = (ShipInterface) fRegistry.lookup(string);
			shipInterface.changeRates(rateGround, rateAir, rateAirPriority);
		}
	}
	
	public Vector<Product> pendingShipments() throws AccessException, RemoteException, NotBoundException {
		this.getServers();
		Vector<Product> vector = new Vector<Product>();
		for (String string : fServers) {
			ShipInterface shipInterface = (ShipInterface) fRegistry.lookup(string);
			vector.addAll(shipInterface.pendingShipments());
		}
		return vector;
	}
	
	public ShipInterface getServer(int index) throws AccessException, RemoteException, NotBoundException {
		if (fServers.size() <= index) {
			return null;
		}
		return (ShipInterface) fRegistry.lookup(fServers.elementAt(index));
	}
}
